package pers.vic.sso.demo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
import pers.vic.sso.client.filter.LoginFilter;
import pers.vic.sso.client.filter.LogoutFilter;
import pers.vic.sso.client.listener.LogoutListener;
import pers.vic.sso.client.session.shiro.ShiroRedisSessionMappingStorage;
import pers.vic.sso.client.session.spring.SpringRedisSessionMappingStorage;
import pers.vic.sso.common.constant.SsoConstant;
import pers.vic.sso.demo.user.service.UserService;

import javax.annotation.Resource;
import javax.servlet.http.HttpSessionListener;
import java.util.ArrayList;
import java.util.List;

/**
 * 描述:
 * 集成单点登录的配置
 *
 * @author Vic.xu
 * @date 2021-11-03 17:09
 */
@Configuration
public class SsoConfig {

    @Value("${sso.server.url}")
    private String serverUrl;
    @Value("${sso.app.id}")
    private String appId;
    @Value("${sso.app.secret}")
    private String appSecret;

    @Value("${sso.html.url}")
    private String htmlUrl;

    @Value("${sso.client.host}")
    private String clientHost;

    @Resource
    private UserService userService;

    /**
     * 单实例方式单点登出Listener,因为它的存储策略就是LocalSessionMappingStorage， 所以无需额外处理
     *
     * @return
     */
    @Bean
    public ServletListenerRegistrationBean<HttpSessionListener> logoutListener() {
        ServletListenerRegistrationBean<HttpSessionListener> listenerRegBean = new ServletListenerRegistrationBean<>();
        LogoutListener logoutListener = new LogoutListener();
        listenerRegBean.setListener(logoutListener);
        return listenerRegBean;
    }

    /**
     * ★★
     * 分布式的登出Listener ，需要为LogoutListener注入 SpringRedisSessionMappingStorage 或 ShiroRedisSessionMappingStorage(而这两种策略均依赖redis) <br />
     * 1. shiro的LogoutListener 注入方式：理应把Listener 放到shiro的SessionManage的sessionListeners中 ，以防止一些调用时机导致的session失效问题，
     *      但是由于LogoutListener实现的是HttpSessionListener 而不是shiro的SessionListener,故此处直接通过spring的方式直接注入<br />
     * 2. spring-session的shiro的LogoutListener注入方式：然后使用Spring的方式注入 LogoutListener，把Listener放进SessionEventHttpSessionListenerAdapter 中防止监听器失效  <br />
     * 以下分别给出示例代码
     */

    /**
     * 分布式spring-redis方式登出Listener:
     * 先注入 SpringRedisSessionMappingStorage ,
     */
//    @Autowired
    private SpringRedisSessionMappingStorage springRedisSessionMappingStorage;

    //    @Bean
    public SpringRedisSessionMappingStorage springRedisSessionMappingStorage() {
        return new SpringRedisSessionMappingStorage();
    }

    //    @Autowired
    private ShiroRedisSessionMappingStorage shiroRedisSessionMappingStorage;

    //    @Bean
    public ShiroRedisSessionMappingStorage shiroRedisSessionMappingStorage() {
        return new ShiroRedisSessionMappingStorage();
    }


    /**
     * 基于shiro 的分布式LogoutListener
     */
//    @Bean
    public ServletListenerRegistrationBean<HttpSessionListener> shiroRedisLogoutListener() {
        ServletListenerRegistrationBean<HttpSessionListener> listenerRegBean = new ServletListenerRegistrationBean<>();
        LogoutListener logoutListener = new LogoutListener();
        //注入session的处理策略为shiro
        logoutListener.setSessionMappingStorage(shiroRedisSessionMappingStorage);
        listenerRegBean.setListener(logoutListener);
        return listenerRegBean;
    }

    /**
     * 基于spring session 的分布式LogoutListener
     */
//    @Bean
    public ApplicationListener<AbstractSessionEvent> springRedisLogoutListener() {
        List<HttpSessionListener> httpSessionListeners = new ArrayList<>();
        LogoutListener logoutListener = new LogoutListener();
        //注入session的处理策略为spring-session
        logoutListener.setSessionMappingStorage(springRedisSessionMappingStorage);
        httpSessionListeners.add(logoutListener);
        return new SessionEventHttpSessionListenerAdapter(httpSessionListeners);
    }


    /**
     * 登录过滤器
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean<LoginFilter> loginFilter() {
        LoginFilter loginFilter = new LoginFilter();
        //前后端分离的登录过滤器
//        SeparationLoginFilter loginFilter = new SeparationLoginFilter(clientHost, htmlUrl);
        loginFilter.setAppId(appId);
        loginFilter.setAppSecret(appSecret);
        loginFilter.setServerUrl(serverUrl);
        loginFilter.addExcludeUrl(SsoConstant.LOGOUT_URL);
        //登录成功之后的回调
        loginFilter.setAfterLogin(accessToken -> {
            userService.afterLogin(accessToken);
        });

        FilterRegistrationBean<LoginFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(loginFilter);
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.setName("loginFilter");
        return filterRegistrationBean;
    }

    /**
     * 登出过滤器
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean<LogoutFilter> logoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setAppId(appId);
        logoutFilter.setAppSecret(appSecret);
        logoutFilter.setServerUrl(serverUrl);
        //登出成功后的回调
        logoutFilter.setAfterLogout(s -> userService.afterLogout(s));

        FilterRegistrationBean<LogoutFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(logoutFilter);
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setName("logoutFilter");
        return filterRegistrationBean;
    }

}
